/**
 * Please modify this class to meet your needs
 * This class is not complete
 */

package com.bah.tract.service;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.WebServiceContext;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.hibernate.exception.ConstraintViolationException;
import org.jboss.wsf.spi.annotation.TransportGuarantee;
import org.jboss.wsf.spi.annotation.WebContext;

import com.bah.tract.mapping.TaskFactory;

@javax.jws.WebService(serviceName = "WorkService", portName = "WorkService", targetNamespace = "http://bah.com/tract/service", wsdlLocation = "META-INF/work-service.wsdl", endpointInterface = "com.bah.tract.service.WorkService")
@Stateless
@WebContext(contextRoot = "tract/services", urlPattern = "/WorkService", transportGuarantee = TransportGuarantee.CONFIDENTIAL)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class WorkServiceBean implements WorkService {

	private static final Logger LOG = Logger.getLogger(WorkServiceBean.class
			.getName());

	@Resource
	private WebServiceContext context;

	@PersistenceContext
	private EntityManager em;

	private List<String> validateWork(Work work) {
		List<String> errors = new ArrayList<String>();
		// GUID is required and unique - handled by the database
		// start is required - handled by the database
		// end is required - handled by the database
		XMLGregorianCalendar start = work.getStart();
		XMLGregorianCalendar end = work.getEnd();
		if (start == null || end == null) {
			errors.add("Both start end times are required.");
		} else if (start.toGregorianCalendar().after(end.toGregorianCalendar())) {
			errors.add("End time must be after the start time.");
		}
		// some tasks are required
		com.bah.tract.model.Project project = em.find(
				com.bah.tract.model.Project.class, work.getProjectId());
		List<com.bah.tract.model.WorkStore> stores = project.getWorkStores();
		Map<Long, com.bah.tract.model.WorkStore> requiredStores = new HashMap<Long, com.bah.tract.model.WorkStore>();
		for (com.bah.tract.model.WorkStore store : stores) {
			if (store.isRequired()) {
				requiredStores.put(store.getId(), store);
			}
		}
		for (Task task : work.getTasks()) {
			requiredStores.remove(task.getStoreId());
		}
		for (com.bah.tract.model.WorkStore store : requiredStores.values()) {
			errors.add("A task from " + store.getLabel() + " is required.");
		}

		return errors;
	}

	private void convertWork(com.bah.tract.model.Work modelWork, Work work)
			throws TractServiceException_Exception {
		try {
			// Convert XML dates to Java dates.
			ConvertUtils.register(new Converter() {

				public Object convert(
						@SuppressWarnings("rawtypes") Class clazz, Object value) {
					XMLGregorianCalendar cal = (XMLGregorianCalendar) value;
					return cal.toGregorianCalendar().getTime();
				}
			}, Date.class);
			// Convert Tasks to specific types
			ConvertUtils.register(new Converter() {

				public Object convert(
						@SuppressWarnings("rawtypes") Class clazz, Object value) {
					@SuppressWarnings("unchecked")
					List<Task> tasks = (List<Task>) value;
					Set<com.bah.tract.model.Task> modelTasks = new HashSet<com.bah.tract.model.Task>();
					for (Task task : tasks) {
						com.bah.tract.model.Task modelTask = TaskFactory
								.getTask(em, task);
						modelTasks.add(modelTask);
					}
					return modelTasks;
				}
			}, Set.class);
			// Determine the project
			com.bah.tract.model.Project project = em.find(
					com.bah.tract.model.Project.class, work.getProjectId());
			if (project == null) {
				throw new TractServiceException_Exception(
						"No project found with that id.");
			}
			modelWork.setProject(project);
			BeanUtils.copyProperties(modelWork, work);
		} catch (IllegalAccessException e) {
			throw new TractServiceException_Exception("Failed to save work", e);
		} catch (InvocationTargetException e) {
			throw new TractServiceException_Exception("Failed to save work", e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.bah.tract.service.WorkService#logWork(com.bah.tract.service.Work
	 * work )*
	 */
	public void logWork(Work work) throws TractServiceException_Exception {
		List<String> errors = validateWork(work);
		if (!errors.isEmpty()) {
			throw new TractServiceException_Exception(errors.toString());
		}
		com.bah.tract.model.Person user = AuthenticationHelper.getUser(context,
				em);
		LOG.info(user.getBoozAllenID() + " called operation logWork.");
		com.bah.tract.model.Work modelWork = new com.bah.tract.model.Work();
		Date now = new Date();
		modelWork.setCreated(now);
		modelWork.setLastUpdate(now);
		modelWork.setPerson(user);
		convertWork(modelWork, work);
		try {
			em.persist(modelWork);
		} catch (PersistenceException e) {
			Throwable t = e.getCause();
			if (t != null) {
				if (t instanceof ConstraintViolationException) {
					TractServiceException ex = new TractServiceException();
					ex.setCode(ErrorType.DUPLICATE_GUID);
					throw new TractServiceException_Exception(
							"Failed to save Work.", ex, t);
				}
			} else {
				throw e;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.bah.tract.service.WorkService#updateWork(com.bah.tract.service.
	 * UpdatedWork updatedWork )*
	 */
	public void updateWork(UpdatedWork updatedWork)
			throws TractServiceException_Exception {
		validateWork(updatedWork);
		com.bah.tract.model.Person user = AuthenticationHelper.getUser(context,
				em);
		LOG.info(user.getBoozAllenID() + " called operation updateWork.");
		if (updatedWork.getReasonForEdit() == null
				|| updatedWork.getReasonForEdit().trim().length() == 0) {
			throw new TractServiceException_Exception(
					"Explaination required for all work modifications.");
		}
		Query query = em.createQuery("select w from Work w where w.GUID=?1");
		query.setParameter(1, updatedWork.getGUID());
		com.bah.tract.model.Work work = (com.bah.tract.model.Work) query
				.getSingleResult();
		// Would have thrown an exception if the work were not found.

		// Make sure that this is the user's work
		if (work.getPerson().getId() != user.getId()) {
			throw new TractServiceException_Exception(
					"Cannot update work logged by another user.");
		}
		convertWork(work, updatedWork);
		work.setEdited(true);
		Date now = new Date();
		work.setLastUpdate(now);
		em.merge(work);
	}

}
